<?php

namespace App\Http\Middleware;

use App\Http\Common\Globals;
use App\Http\Common\MyUtils;
use Closure;
use Illuminate\Contracts\Routing\ResponseFactory;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Session;
use Predis\Client;
use Tymon\JWTAuth\Exceptions\JWTException;
use Tymon\JWTAuth\Exceptions\TokenExpiredException;
use Tymon\JWTAuth\Exceptions\TokenInvalidException;
use Tymon\JWTAuth\Facades\JWTAuth;
use function EasyWeChat\Kernel\Support\get_client_ip;

class CheckToken
{
    /**
     * Handle an incoming request.
     * @param  Request  $request
     * @param Closure $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $filter = $this->returnFilter();
        // redis 配置文件是否重新刷新
        if (env("REDIS_FLUSH_CONFIG")){// 清除的redis配置文件
            $redis = new Client();
            $keys = $redis->keys(config('database.redis.options.prefix').'my_config*');
            foreach ($keys as $k=>$v){
                $redis->del($v);
            }
        }
        if (env("REDIS_FLUSH")){// 清除所有的redis文件
            $redis = new Client();
            $keys = $redis->keys(config('database.redis.options.prefix').'*');
            foreach ($keys as $k=>$v){
                $redis->del($v);
            }
        }
        // 拿到路由名称 然后匹配改路由 是否需要token验证
        if (MyUtils::inExceptArray($request,$filter)){
            //刚刚登陆就清除所有配置文件，防止写死了
            return $next($request);
        }else {
            /**
             * tokenType 为 jwt 就是走jwt,如果是session就是走旧的接口
             */
            if ($request->all()){
                if ($request->errorMessage){
                    return $next($request);
                }
                switch ($request->tokenType){
                    case "session":
                        $res = $this->checkOld($request, $next);
                        break;
                    case "jwt":
                    default:
                        config(['auth.guards.api.driver' => 'jwt']);
                        $res = $this->checkNew($request, $next);
                        break;
                }
                return $res;
            }
            return $next($request);
        }
    }

    /**
     * 老的根据session来实现的 登录操作
     * @param $request
     * @param Closure $next
     * @return ResponseFactory|Response|mixed
     */
    public function checkOld($request, Closure $next){
        // 判断头是否有 project-id
        $header = getallheaders();
        $headToken = MyUtils::sg($header,'Access-Token');
        MyUtils::setRequest($request,'token',$headToken);
        if($headToken!=env('WANNENG_TOKEN')){
            $PROJECT_ID = $this->getProjectId($header,$request);
            // 正确就实现路由操作，不正确就返回_token错误，传递了就验证session里面的_token值是否正确
            $allowAdmin = MyUtils::inExceptArray($request,$this->allowAdminIn());
            $allowFront = MyUtils::inExceptArray($request,$this->allowFrontIn());
            $sessionToken = [];
            if ($allowAdmin){
                $sessionToken1 = Session::get("token");
                $sessionToken[] = $sessionToken1;
            }
            if ($allowFront){
                $sessionToken2 = Session::get("_token");
                $sessionToken[] = $sessionToken2;
            }
            if (count($sessionToken)==0){// token为空，
                return response(MyUtils::dealFD("会话不一致，请重新登录，请尝试“重新登录”。",-2));
            }
            if ( !in_array($headToken,$sessionToken)){
                return response(MyUtils::dealFD("token不一致，用户没有登录，请重新登录！",-2));
            }
            // 不需要查询数据库 ，速度较快 ,但是不太安全，根据后期需求觉得是否启用
            $openid = Session::get('openid');// 为空表示是游客
            if ($openid){
                $ifChangePassword = Session::get($openid)!==Redis::get('user_openids:'.$openid);
                if ($ifChangePassword){// 修改了密码
                    Session::flush();
                    return response(MyUtils::dealFD("用户名密码被修改，请重新登录！",-2));
                }
                $ifChangeLoginAdress = (''.Session::get('login_time')) === Redis::get('user_login_time:'.$openid);
                if (env('SINGLE_LOGIN')
                    && !empty(Session::get('login_time'))
                    && !$ifChangeLoginAdress){// 在其他地点登录
                    Session::flush();
                    return response(MyUtils::dealFD("提示：账号在另一台设备上登录，您的账号密码可能已泄露",-2));
                }
                if (!Session::get('user_id')){// 用户id没了请退出
                    return response(MyUtils::dealFD("session的用户user_id失效，请重新登录！",-2));
                }
                // 正确就实现路由操作，不正确就返回_token错误
                // 传递了就验证session里面的_token值是否正确
                // token写入的时间
                $sessionTokenInTime = Session::get("token_in_time");
                // token的过期时间
                $sessionTokenTime = Session::get("token_time");
                // 验证session的时间已经过期了，就退出
                if (time()-$sessionTokenTime-$sessionTokenInTime>=0){
                    Session::flush();// session到期，清除所有的session退出
                    return response(MyUtils::dealFD("Token超过三小时，已过期，请重新登录！",-2));
                }
            }
            if ($PROJECT_ID!= MyUtils::getSessionConfig('PROJECT_ID')){
                return response(MyUtils::dealFD("请求失败，project_id没有设置。",-2));
            }
        }
        return $next($request);
    }

    /**
     * 新增 根据jwt来实现登录的操作
     * @param $request
     * @param Closure $next
     * @return ResponseFactory|Response|mixed
     */
    public function checkNew($request, Closure $next){
        // 判断头是否有 project-id
        $header = getallheaders();
        $headToken = MyUtils::sg($header,'Access-Token');
        MyUtils::setRequest($request,'token',$headToken);
        //验证Token
        if (!isset($request->token)) {
            //反馈失效
            return response(MyUtils::dealFD(-2, "请输入token"));
        }else{
            try {
                if (! $user = JWTAuth::parseToken()->authenticate()) {  //获取到用户数据，并赋值给$user
                    return response(MyUtils::dealFD('',-1,[
                        'retmsg' => '没有发现该用户'
                    ]));
                }
                Session::put('user',$user);// 放入session
                Session::save();
                //如果想向控制器里传入用户信息，将数据添加到$request里面
                return $next($request);
            } catch (TokenExpiredException $e) {
                return response(MyUtils::dealFD('',-2,[
                    'retmsg' => 'token 过期' , //token已过期
                ]));
            } catch (TokenInvalidException $e) {
                return response(MyUtils::dealFD('',-2,[
                    'retmsg' => 'token 无效',  //token无效
                ]));
            } catch (JWTException $e) {
                return response(MyUtils::dealFD('',-2,[
                    'retmsg' => '缺少token' , //token为空
                ]));
            }
        }
    }

    /**
     * 得到projectId
     * @param $header
     * @param $request
     * @return string
     */
    public function getProjectId($header,$request){
        $PROJECT_ID1 = MyUtils::sg($header,'project-id');
        $PROJECT_ID2 = MyUtils::sg($header,'Project-Id');
        $PROJECT_ID3 = MyUtils::sg($request,'project-id');
        $PROJECT_ID4 = MyUtils::sg($request,'Project-Id');
        $PROJECT_ID = '';
        if ($PROJECT_ID1){$PROJECT_ID = $PROJECT_ID1;}
        if ($PROJECT_ID2){$PROJECT_ID = $PROJECT_ID2;}
        if ($PROJECT_ID3){$PROJECT_ID = $PROJECT_ID3;}
        if ($PROJECT_ID4){$PROJECT_ID = $PROJECT_ID4;}
        return $PROJECT_ID;
    }

    /**
     * 返回不需要过滤的路由
     */
    public function returnFilter(){
        return [
            "login",// 登录，不用验证
            "logout",// 登出，不用验证
            "api/*",// saas对外的接口，不用验证
            "tools/*",// 工具 不需要token验证
            "user/ifOauthWechatUser",// 开放对外界的资源接口 不需要token验证
            "user/oauthWechatUser",// 开放对外界的资源接口 不需要token验证
            "auth/*",// 开放对外界的资源接口 不需要token验证
        ];
    }

    /**
     * 返回所有后端的路由 可以除去不需要token 验证的
     */
    public function allowAdminIn(){
        return [
        ];
    }
    /**
     * 返回所有前端的路由 可以除去不需要token 验证的
     */
    public function allowFrontIn(){
        return [
        ];
    }


}
